/*
 * NormalFitness.java
 *
 * Created on den 30 mars 2007, 23:30
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package grex.fitnessfunctions.Classification;

import grex.BNFException;
import grex.ErrorManager;
import grex.EvolutionListener;
import grex.EvolutionProcess;
import grex.GP;
import grex.Options;
import grex.Prediction;
import grex.fitnessfunctions.FitnessFunction;
import grex.fitnessfunctions.IFitnessFunction;
import grex.genes.Gene;
import grex.genes.GeneException;
import grex.genes.If;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import weka.core.Instances;
import wekaGrexBridge.J48Injection;
import wekaGrexBridge.WekaArffTableModel;

/**
 *
 * @author rik
 */
public class MultipleSolutionsFitness extends FitnessFunction implements IFitnessFunction, Serializable, EvolutionListener {


    private GP bestGP = null;
    private GP worstOfBestGP = null;
    public CopyOnWriteArrayList<GP> winners;
    int calculations = 0;
    double avgFitness = 0;
    double bestAcc = Double.MAX_VALUE;
    double MAX_ACC_DIFF = 0.98;
    double MAX_FIT_DIFF = 0.9;
    private double MAX_Error = 97;
    private int MAX_IFs = 5;
    double benchmarkACC = 0;
    private double gpIfs = 0;
    private GP j48 = null;

    public MultipleSolutionsFitness() {
        super("MultipleSolutions");
    }

    public void resetStatistics() {
        bestAcc = Double.MAX_VALUE;
        avgFitness = 0;
        calculations = 0;
        worstOfBestGP = null;
        bestGP = null;
        oldNrOfWinner = 0;
    }

    public GP getJ48GP() {
        return j48;
    }
    @Override
    protected double calcPredictionError(Prediction prediction, double targetValue) {
       return prediction.getPrediction() == targetValue ? 0 : 1;
    }

    @Override
    public synchronized double calcFitness(GP gp) throws GeneException {

        Options ops = gp.getOptions();

        gpIfs = gp.calcIFs();
        double trainFitness = 0;
        calculations++;


        double like = 0;
        //like = winnerSim(gp);

        gp.train();        
       // gp.clean();
        //Calculate the error punishment
        double trainError = gp.getTrainAccError();
        double bonusError = Math.max((MAX_Error-trainError), 0);
        double error = Math.max(trainError-MAX_Error, 0);
        trainFitness = error *1000 + bonusError * 100;// trainError * 1000 + error*1000;// + bonusError * 100;

        //Calculate the size punishment
        int ifs = gp.calcIFs();
        double minIfs = Math.min(MAX_IFs-ifs, 0);
        double overIfs = Math.max(ifs - MAX_IFs, 0);
        trainFitness += minIfs * 10 + overIfs * 50;// * ops.getPARAMETER_4();

        
       // if (ifs <= MAX_IFs) {

        //}
        //If the tree is ot good enough it gets the same "like" punishment as if it was identical
        //to another solution. This is done to save time.
        if (trainError > MAX_Error || ifs > MAX_IFs) {
            like += MAX_IFs;
            gp.setLikness(like);
            trainFitness += like;
            return trainFitness;
        }

        if(winners!=null&&winners.size()>0){
            like = cleanAddcalcLike(gp);
            gp.setLikness(like);
            trainFitness += like;
            if (oldNrOfWinner < winners.size()) {
                System.out.println("Generation:" + calculations / ops.getPOPULATION_SIZE() + "nrOfwinners = " + winners.size() + " MinAcc" + MAX_Error + "MaxIfs" + MAX_IFs);
                oldNrOfWinner = winners.size();
            }
        }
        return trainFitness;
    }
    int oldNrOfWinner = 0;

    public void reset() {
        j48 = null;
    }

    private double cleanAddcalcLike(GP gpi) {
        GP gp = (GP) gpi.clone();
        gp.clean();
        int gpIFs = gp.calcIFs();
        //  if(gp.getLength()>benchmarkLength)
        if (gpIFs > MAX_IFs) {
            return gpIFs;
        }
        //gp.removeDuplicates()

            gp.train();
        double maxE = 0;
        for (int i = 0; i < winners.size(); i++) {
            GP igp = winners.get(i);
            if (gp != igp) {
                double e = equality(gp.getHead(), igp.getHead());
                int igpIFs = igp.calcIFs();
                if (e > maxE) {
                    maxE = e;
                }
                
                if (e == gpIFs) {
                    if (gpIFs >= igpIFs) {
                        //System.out.println("Copy, not added");
                        return e;
                    } else {
                        System.out.println("Removed Tree");
                        winners.remove(i);
                        i--;
                    }
                }
            }
        }
        winners.add(gp);
        return maxE;
    }

    
    private int equality(Gene g1, Gene g2) {
        int e = 0;
        if (g1 == null || g2 == null) {
            return 0;
        }
        if (g1 instanceof If && g2 instanceof If) {
            Gene[] children1 = g1.getChildren();
            Gene[] children2 = g2.getChildren();
            if (children1[0].equals(children2[0])) {
                e++;
            }
            e += equality(children1[1], children2[1]);
            e += equality(children1[2], children2[2]);
        }
        return e;
    }


    /**
     * @return the MIN_ACC
     */
    public double getMAX_ACC_ERROR() {
        return MAX_Error;
    }

    /**
     * @param MIN_ACC the MIN_ACC to set
     */
    public void setMAX_ACC_ERROR(double MAX_ACC_ERROR) {
        this.MAX_Error = MAX_ACC_ERROR;
    }

    /**
     * @return the MAX_IFs
     */
    public int getMAX_IFs() {
        return MAX_IFs;
    }

    /**
     * @param MAX_IFs the MAX_IFs to set
     */
    public void setMAX_IFs(int MAX_IFs) {
        this.MAX_IFs = MAX_IFs;
    }
    
    public void evolutionStarted(EvolutionProcess source) {
        /*      Options ops = source.getOptions();
        MAX_Error = ops.getPARAMETER_2();
        MAX_IFs = (int) ops.getPARAMETER_3();
        if (ops.getWinners() == null){
        winners = new CopyOnWriteArrayList<GP>();
        ops.setWinners(winners);
        resetStatistics();
        }*/
        //Beräkning av acc är nu beräkning av felet så denna funktion fungerar inte dessutoms så ligger accen nu mellan 0-100 ch inte 0.1 
        Options ops = source.getOptions();
        if (ops.getWinners() == null || j48 == null) {
            winners = new CopyOnWriteArrayList<GP>();
            ops.setWinners(winners);
            resetStatistics();
            
            WekaArffTableModel wm = new WekaArffTableModel(new J48Injection(), source.getData());
            Instances wekaTrain = wm.getTrainInstances(ops.getTestFOLD());
            
            j48 = wm.createJ48GP(wekaTrain, 0.25f, source.getEnvironment());
            
            
            for (int d = (int) ops.getPARAMETER_3(); d >= 0 && j48.calcIFs() > ops.getPARAMETER_3(); d--) {
                j48.cutTree(d);
            }
//  */        
            setMAX_IFs(j48.calcIFs());
          /*  try {
                source.getWorld().getPopulation().injectGP(j48, (int) ops.getPARAMETER_3() * 5);
            } catch (Exception ex) {
                ErrorManager.getInstance().reportError(ex);
            }//*/
            
            try {
                j48.getHead().setModified(true);
                j48.getHead().notifyChildrenOfModification();
                
                j48.train();
                setMAX_ACC_ERROR(j48.getTrainAccError());
                winners.add(j48);
                System.out.println("Fold" + ops.getTestFOLD() + " Initial J48 Tree. Train Acc:" + j48.getTrainAccError() + " Ifs" + MAX_IFs + "\n" + j48.getFormatedTree());
            } catch (GeneException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void generationTerminated(EvolutionProcess source) {
    }

    public void batchTerminated(EvolutionProcess source) {
    }

    public void evolutionTerminated(EvolutionProcess source) {
    }

    public void evolutionProcessTerminated(EvolutionProcess source) {
  }
    
    
      /*  public void recalculateFitness(GP gp) throws GeneException {
        double like = 0;
        //like = winnerSim(gp);
        like = winnerMaxEquality(gp);
        gp.setLikness(like);
        gp.train();
        double train = 100 - gp.getTrainAccError();
        double fitness = 1000 * train + gp.getLength() * 10000 * gp.getOptions().getPUNISHMENT_FOR_LENGTH() + like;
        gp.setFitness(fitness);
    }

    private int winnerMaxEquality(GP gp) {
        int maxSim = 0;
        for (GP igp : winners) {
            if (igp != gp) {
                int treeSim = equality(gp.getHead(), igp.getHead());
                if (treeSim > maxSim) {
                    maxSim = treeSim;
                }
                if (treeSim == gp.calcIFs() || treeSim == igp.calcIFs()) {
                    try {
                        //System.out.println("Identical tree!");
                        if (gp.getTrainAccError() < igp.getTrainAccError()) {
                            return gp.calcIFs() / 2;
                        } else {
                            return gp.calcIFs() * 2;
                        }
                    } catch (GeneException ex) {
                        Logger.getLogger(MultipleSolutionsFitness.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        return maxSim;
    }
*/


}
